package com.hooker.base;

import android.app.Activity;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.Camera;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.BatteryManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.StrictMode;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import android.widget.Toast;

import com.hooker.common.HookHelper;
import com.hooker.common.Reflect;
import com.hooker.common.Reflection;
import com.hooker.helper.FilterEnum;
import com.hooker.helper.RequestStatsManager;
import com.hooker.settings.HLog;
import com.hooker.settings.Setting;
import com.hooker.settings.SettingHelper;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public abstract class BaseHooker extends BaseXposedLoader {

    protected ClassLoader loader;

    public static String concatParams(XC_MethodHook.MethodHookParam params) {
        StringBuilder sb = new StringBuilder();
        Object[] args = params.args;
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                if (arg != null) {
                    sb.append(arg.getClass().toString());
                    sb.append(" --> ");
                    sb.append(arg);
                    sb.append("\n");
                } else {
                    sb.append("this arg is null \n");
                }
            }
        }
        return sb.toString();
    }

    protected String getBundleInfo(Bundle bundle) {
        StringBuilder builder = new StringBuilder();
        if (bundle != null) {
            Iterator<String> iterator = bundle.keySet().iterator();
            while (iterator.hasNext()) {
                String key = iterator.next();
                Object value = bundle.get(key);
                builder.append("key=" + key + ",value=" + value + "\n");
            }
        }
        return builder.toString();
    }

    protected String getIntentInfo(Intent intent) {
        if (intent != null) {
            Bundle bundle = intent.getExtras();
            String bundleInfo = getBundleInfo(bundle);
            return intent.toString().replace("(has extras)", "{" + bundleInfo + "}");
        }
        return "";
    }

    protected boolean has(FilterEnum.CategoryType type, String name) {
        if (!isEnable() || type == null) {
            return false;
        }
        return has(type.getName(), name);
    }

    /**
     * @param category 类别(类，方法，路径)
     * @param name     (类别对应的名字)
     * @return
     */
    protected boolean has(String category, String name) {
        if (!isEnable()) {
            return false;
        }
        return containContent(category, name) && startWithContent(category, name) && endWithContent(category, name) && regexContent(category, name);
    }

    protected boolean isEnable() {
        String packageName = loadParams.packageName;
        String tag = SettingHelper.getBuildInfo(packageName, getTag());
        String state = Setting.get(context, tag);
        return "1".equals(state);
    }

    protected boolean containContent(String category, String name) {
        if (name == null || "".equals(name)) {
            return true;
        }

        String packageName = loadParams.packageName;
        String tag = SettingHelper.getBuildInfo(packageName, getTag(), category, FilterEnum.FilterType.INCLUDE.getName());
        List<String> list = SettingHelper.getInfo(context, tag);
        if (list.isEmpty()) {
            return true;
        }
        for (String s : list) {
            if (s != null && name.toLowerCase().contains(s.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    protected boolean startWithContent(String category, String name) {
        String packageName = loadParams.packageName;
        String tag = SettingHelper.getBuildInfo(packageName, getTag(), category, FilterEnum.FilterType.START.getName());
        List<String> list = SettingHelper.getInfo(context, tag);
        if (list.isEmpty()) {
            return true;
        }
        for (String s : list) {
            if (s != null && name.toLowerCase().startsWith(s.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    protected boolean endWithContent(String category, String name) {
        String packageName = loadParams.packageName;
        String tag = SettingHelper.getBuildInfo(packageName, getTag(), category, FilterEnum.FilterType.END.getName());
        List<String> list = SettingHelper.getInfo(context, tag);
        if (list.isEmpty()) {
            return true;
        }
        for (String s : list) {
            if (s != null && name.toLowerCase().endsWith(s.toLowerCase())) {
                return true;
            }
        }
        return false;
    }

    protected boolean regexContent(String category, String name) {
        if (name == null || "".equals(name)) {
            return true;
        }

        String packageName = loadParams.packageName;
        String tag = SettingHelper.getBuildInfo(packageName, getTag(), category, FilterEnum.FilterType.REGEX.getName());
        List<String> list = SettingHelper.getInfo(context, tag);
        if (list.isEmpty()) {
            return true;
        }
        for (String s : list) {
            if (s != null) {
                Pattern pattern = Pattern.compile(s);
                if (pattern.matcher(name).matches()) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void hook(Context context, XC_LoadPackage.LoadPackageParam loadParams) {

    }

    public abstract String getTag();

    @Override
    protected void log(String msg) {
        String tag = getTag();
        if (tag == null) {
            HLog.o(msg);
        } else {
            HLog.o(getTag(), msg);
        }
    }

    public BaseHooker() {
    }

    public BaseHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
        super(context, lparam);
        if (lparam != null && lparam.classLoader != null) {
            loader = lparam.classLoader;
        } else {
            loader = getClass().getClassLoader();
        }
    }

    public interface HookMethodListener {
        void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable;

        void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable;
    }

    public interface CallBack<T> {
        T call(Object object);
    }

    protected void callBack(XC_MethodHook.MethodHookParam param, Boolean isBefore, Object arg, Object... calls) {
        if (calls != null && calls.length > 0) {
            for (Object object : calls) {
                if (object instanceof HookMethodListener && isBefore != null) {
                    try {
                        HookMethodListener method = (HookMethodListener) object;
                        if (isBefore) {
                            method.beforeHookedMethod(param);
                        } else {
                            method.afterHookedMethod(param);
                        }
                    } catch (Throwable throwable) {
                        throwable.printStackTrace();
                    }
                } else if (object instanceof CallBack) {
                    ((CallBack) object).call(arg);
                }
            }
        }
    }

    protected void callBeforeHookedMethod(XC_MethodHook.MethodHookParam param, Object... calls) {
        callBack(param, true, null, calls);
    }

    protected void callAfterHookedMethod(XC_MethodHook.MethodHookParam param, Object... calls) {
        callBack(param, false, null, calls);
    }

    public static class UnXposedHooker extends BaseHooker {

        final String XPOSED_HELPERS = "de.robv.android.xposed.XposedHelpers";
        final String XPOSED_BRIDGE = "de.robv.android.xposed.XposedBridge";

        final static List<String> hookPackages = new ArrayList<>();
        final static List<String> versionExcludes = new ArrayList<>();

        static {
            hookPackages.add("de.robv.android.xposed.installer");
            hookPackages.add("com.saurik.substrate");
            hookPackages.add("com.solohsu.android.edxp.manager");
        }

        static {
            versionExcludes.add("8.1.0");
        }

        public UnXposedHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        @Override
        protected void log(String msg) {
//            super.log(msg);
        }

        @Override
        public void hook(Object... calls) {
            final boolean oldAdb = (Settings.Global.getInt(context.getContentResolver(), Settings.Global.ADB_ENABLED, 0) > 0);
            hookUnXposed(calls);
            final boolean enableAdb = (Settings.Global.getInt(context.getContentResolver(), Settings.Global.ADB_ENABLED, 0) > 0);
            String text = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
            HookHelper.submitUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(context, "原先是：" + (oldAdb ? "开发者" : "不是开发者") + "，同步修改为：" + (enableAdb ? "开发者" : "不是开发者"), Toast.LENGTH_SHORT).show();
                }
            });
            log("androidId=" + text);
        }

        public void hookUnXposed(final Object... calls) {
            //系统属性
            hookMethod("android.os.SystemProperties", "get", String.class, String.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    String name = (String) param.args[0];
                    String result = (String) param.getResult();
                    handleSystemProperties(param, name, result);

                    callAfterHookedMethod(param, calls);
                }
            });

            hookMethod("android.os.SystemProperties", "get", String.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    String name = (String) param.args[0];
                    String result = (String) param.getResult();
                    handleSystemProperties(param, name, result);

                    callAfterHookedMethod(param, calls);
                }
            });

            //应用调试模式
            hookMethod(Debug.class, "isDebuggerConnected", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    param.setResult(false);
                    callAfterHookedMethod(param, calls);
                }
            });

            //是否是开发者
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
                hookMethod(Settings.Global.class, "getStringForUser", ContentResolver.class, String.class, int.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        if (param.args != null && param.args.length > 1) {
                            String name = (String) param.args[1];
                            if (Settings.Global.ADB_ENABLED.equals(name)) {
                                param.setResult("0");
                            }
                            // 开发者模式
                            if (Settings.Global.DEVELOPMENT_SETTINGS_ENABLED.equals(name)) {
                                param.setResult("0");
                            }
                        }
                        callAfterHookedMethod(param, calls);
                    }
                });
            } else {
                hookMethod(Settings.Secure.class, "getStringForUser", ContentResolver.class, String.class, int.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        if (param.args != null && param.args.length > 1) {
                            String name = (String) param.args[1];
                            if (Settings.Secure.ADB_ENABLED.equals(name)) {
                                param.setResult("0");
                            }
                            // 开发者模式
                            if (Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED.equals(name)) {
                                param.setResult("0");
                            }
                        }
                        callAfterHookedMethod(param, calls);
                    }
                });
            }

            hookStackTrace(calls);

            hookInstallApps(calls);

            hookUSBInfo(calls);

            hookStream(calls);

            hookClassLoader(calls);

            hookDevice(calls);

            hookRoot(calls);

            hookParams(calls);

//            hookMethodInvoke(calls);
        }

        private void handleSystemProperties(XC_MethodHook.MethodHookParam param, String name, String result) {
            //基带版本
            if ("gsm.version.baseband".equals(name)) {
                if (TextUtils.isEmpty(result) || "1.0.0.0".equals(result)) {
                    param.setResult("test-1.2.3.4.5");
                }
            }

            if ("ro.build.flavor".equals(name)) {
                if (TextUtils.isEmpty(result) || result.contains("vbox") || result.contains("sdk_gphone")) {
                    param.setResult("release-flavor");
                }
            }

            if ("ro.product.board".equals(name)) {
                if (TextUtils.isEmpty(result) || result.contains("android") || result.contains("goldfish")) {
                    param.setResult("release-board");
                }
            }

            if ("ro.board.platform".equals(name)) {
                if (TextUtils.isEmpty(result) || result.contains("android")) {
                    param.setResult("release-platform");
                }
            }

            if ("ro.hardware".equals(name)) {
                if (TextUtils.isEmpty(result) || result.toLowerCase().contains("ttvm") || result.toLowerCase().contains("nox")) {
                    param.setResult("release-harware");
                }
            }

            if ("ro.secure".equals(name)) {
                if (TextUtils.isEmpty(result) || result.equals("0")) {
                    param.setResult("1");
                }
            }

            if ("ro.debuggable".equals(name)) {
                if (TextUtils.isEmpty(result) || result.equals("1")) {
                    param.setResult("0");
                }
            }

            if ("ro.build.type".equals(name)) {
                if (TextUtils.isEmpty(result) || result.equals("userdebug") || result.equals("debug") || "eng".equals(result)) {
                    param.setResult("release");
                }
            }

            if ("ro.build.tags".equals(name)) {
                if (TextUtils.isEmpty(result) || result.contains("test-keys")) {
                    param.setResult("release-keys");
                }
            }
        }

        private void hookMethodInvoke(final Object[] calls) {
            hookMethod(Method.class.getName(), "invoke", Object.class, Object[].class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    Method method = (Method) param.thisObject;
                    if (method != null) {
                        String className = method.getDeclaringClass().getName();
                        String mName = method.getName();
                        handleInReflect(param, className, mName);
                    }
                    callAfterHookedMethod(param, calls);
                }

                /**
                 * 处理反射信息
                 * @param param
                 * @param className
                 * @param mName
                 */
                private void handleInReflect(MethodHookParam param, String className, String mName) {
                    if ("android.os.SystemProperties".equals(className) && "get".equals(mName)) {
                        handleSystemPropertiesInReflect(param);
                    }
                }

                /**
                 * 处理反射中的SystemProperties
                 * @param param
                 */
                private void handleSystemPropertiesInReflect(MethodHookParam param) {
                    if (param.args != null && param.args.length > 1) {
                        Object[] args = (Object[]) param.args[1];
                        if (args != null && args.length > 0) {
                            handleSystemProperties(param, String.valueOf(args[0]), (String) param.getResult());
                        }
                    }
                }
            });
        }

        private void hookParams(final Object[] calls) {
            hookMethod(JSONObject.class, "opt", String.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    String name = (String) param.args[0];
                    if ("noShields".equals(name)) {
                        param.setResult("0");
                    }
                    callAfterHookedMethod(param, calls);
                }
            });
        }

        //是否root
        private void hookRoot(final Object[] calls) {
            final String[] known_bluestacks = {"/data/app/com.bluestacks.appmart-1.apk", "/data/app/com.bluestacks.BstCommandProcessor-1.apk",
                    "/data/app/com.bluestacks.help-1.apk", "/data/app/com.bluestacks.home-1.apk", "/data/app/com.bluestacks.s2p-1.apk",
                    "/data/app/com.bluestacks.searchapp-1.apk", "/data/bluestacks.prop", "/data/data/com.androVM.vmconfig",
                    "/data/data/com.bluestacks.accelerometerui", "/data/data/com.bluestacks.appfinder", "/data/data/com.bluestacks.appmart",
                    "/data/data/com.bluestacks.appsettings", "/data/data/com.bluestacks.BstCommandProcessor", "/data/data/com.bluestacks.bstfolder",
                    "/data/data/com.bluestacks.help", "/data/data/com.bluestacks.home", "/data/data/com.bluestacks.s2p", "/data/data/com.bluestacks.searchapp",
                    "/data/data/com.bluestacks.settings", "/data/data/com.bluestacks.setup", "/data/data/com.bluestacks.spotlight", "/mnt/prebundledapps/bluestacks.prop.orig"
            };

            final String[] rootPaths = {"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su",
                    "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
                    "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su", "/system/sbin/su", "/vendor/bin/su"};

            //是否root
            hookMethod(File.class, "exists", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    File file = (File) param.thisObject;
                    String filePath = file.getAbsolutePath();
                    if (filePath == null || filePath.toLowerCase().contains("xpose")) {
                        param.setResult(false);
                        return;
                    }

                    for (String path : known_bluestacks) {
                        if (filePath.contains(path)) {
                            param.setResult(false);
                            break;
                        }
                    }

                    for (String path : rootPaths) {
                        if (filePath.contains(path)) {
                            param.setResult(false);
                            break;
                        }
                    }

                    //判断是否存在adb
                    if (filePath.toLowerCase().contains("data/misc/adb")) {
                        param.setResult(false);
                    }
                    callAfterHookedMethod(param, calls);
                }
            });
        }

        //设备信息
        private void hookDevice(final Object[] calls) {
            //wifi代理
            hookMethod(System.class, "getProperty", String.class, new XC_MethodHook() {

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    String name = (String) param.args[0];
                    if ("http.proxyHost".equals(name) || "http.proxyPort".equals(name)) {
                        param.setResult(null);
                    }
                    callAfterHookedMethod(param, calls);
                }
            });

            //相机
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                hookMethod(Camera.class, "getNumberOfCameras", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        int result = (int) param.getResult();
                        if (result <= 0) {
                            param.setResult(1);
                        }
                        callAfterHookedMethod(param, calls);
                    }
                });
            } else {
                hookMethod(CameraManager.class, "getCameraIdList", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        String[] result = (String[]) param.getResult();
                        if (result == null || result.length == 0) {
                            param.setResult(new String[1]);
                        }
                        super.afterHookedMethod(param);
                        callAfterHookedMethod(param, calls);
                    }
                });
            }

            //vpn
            hookMethod(NetworkInterface.class, "getName", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    String name = (String) param.getResult();
                    if ("tun0".equals(name) || "ppp0".equals(name)) {
                        param.setResult("test0");
                    }
                    super.afterHookedMethod(param);
                    callAfterHookedMethod(param, calls);
                }
            });

            //判断是否包含SIM卡
            hookMethod(TelephonyManager.class, "getSimState", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    if (param.getResult() == null) {
                        return;
                    }
                    int state = (int) param.getResult();
                    if (state == TelephonyManager.SIM_STATE_ABSENT ||
                            state == TelephonyManager.SIM_STATE_UNKNOWN) {
                        param.setResult(TelephonyManager.SIM_STATE_READY);
                    }
                    super.afterHookedMethod(param);
                    callAfterHookedMethod(param, calls);
                }
            });

            //wifi数量
            hookMethod(WifiManager.class, "getConfiguredNetworks", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    List<WifiConfiguration> list = (List<WifiConfiguration>) param.getResult();
                    while (list != null && list.size() <= 1) {
                        list.add(new WifiConfiguration());
                    }
                    if (list != null) {
                        param.setResult(list);
                    }
                    super.afterHookedMethod(param);
                    callAfterHookedMethod(param, calls);
                }
            });

            //是否存在解锁密码
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                hookMethod(KeyguardManager.class, "isKeyguardSecure", new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        super.afterHookedMethod(param);
                        param.setResult(true);
                        callAfterHookedMethod(param, calls);
                    }
                });
            } else {
                hookMethod(Settings.System.class, "getInt", ContentResolver.class, String.class, int.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        super.afterHookedMethod(param);
                        if (param.args != null && param.args.length > 1) {
                            String name = (String) param.args[1];
                            if (Settings.System.LOCK_PATTERN_ENABLED.equals(name)) {
                                param.setResult(1);
                            }
                        }
                        callAfterHookedMethod(param, calls);
                    }
                });
            }
        }

        //hook classloader
        private void hookClassLoader(final Object[] calls) {
            //是否安装xposed
            ClassLoader parent = getClass().getClassLoader().getParent();
            if (parent != null && parent.toString().contains("/system/framework/edxp.jar")) {

            } else if (!versionExcludes.contains(Build.VERSION.RELEASE)) {
                hookMethod(ClassLoader.class, "loadClass", String.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        if (param.hasThrowable()) {
                            return;
                        }

                        if (param.args.length != 1) {
                            return;
                        }

                        Class<?> cls = (Class<?>) param.getResult();
                        String name = cls.getName();
                        log("class.loader=" + name);

                        if (XPOSED_BRIDGE.equals(name) || XPOSED_HELPERS.equals(name)) {
                            param.setThrowable(new ClassNotFoundException("can not load class"));
                        }
                        callAfterHookedMethod(param, calls);
                    }
                });
            }
        }

        //流相关
        private void hookStream(final Object[] calls) {
            hookMethod(DataOutputStream.class, "writeBytes", String.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    String code = (String) param.args[0];
                    callAfterHookedMethod(param, calls);
                }
            });

            hookMethod(Runtime.class, "exec", String.class, String[].class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                    String cmd = (String) param.args[0];
                    log("exec.cmd=" + cmd);
                    callAfterHookedMethod(param, calls);
                }
            });

            hookMethod(BufferedReader.class, "readLine", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    handleReadLine(param, calls);
                    callAfterHookedMethod(param, calls);
                }
            });

            hookMethod(DataInputStream.class, "readLine", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    handleReadLine(param, calls);
                    super.afterHookedMethod(param);
                    callAfterHookedMethod(param, calls);
                }
            });
        }

        //usb信息
        private void hookUSBInfo(final Object[] calls) {
            hookMethod(Intent.class, "getIntExtra", String.class, int.class, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    if (param.getResult() == null) {
                        return;
                    }
                    Intent intent = (Intent) param.thisObject;
                    int result = (int) param.getResult();

                    Object name = param.args[0];
                    if (BatteryManager.EXTRA_PLUGGED.equals(name)) {
                        param.setResult(0);
                    }

                    if (BatteryManager.EXTRA_STATUS.equals(name)) {
                        if (result == BatteryManager.BATTERY_STATUS_CHARGING ||
                                result == BatteryManager.BATTERY_STATUS_FULL) {
                            param.setResult(-1);
                        }
                    }

                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                        if (intent != null && "Intent.ACTION_BATTERY_CHANGED".equals(intent.getAction())) {
                            if (BatteryManager.EXTRA_LEVEL.equals(name)) {
                                param.setResult(95);
                            }
                        }
                    }
                    callAfterHookedMethod(param, calls);
                }
            });

            //判断电量
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                hookMethod(BatteryManager.class, "getIntProperty", int.class, new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        int prop = (int) param.args[0];
                        if (BatteryManager.BATTERY_PROPERTY_CAPACITY == prop) {
                            param.setResult(95);
                        }
                        super.afterHookedMethod(param);
                        callAfterHookedMethod(param, calls);
                    }
                });
            }

            hookMethod(Intent.class, "getExtras", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    Intent intent = (Intent) param.thisObject;
                    Bundle bundle = (Bundle) param.getResult();
                    if (intent != null && "android.hardware.usb.action.USB_STATE".equals(intent.getAction())) {
                        bundle.putBoolean("connected", false);
                        bundle.putBoolean("adb", false);
                        param.setResult(bundle);
                    }
                    callAfterHookedMethod(param, calls);
                }
            });
        }

        //安装应用
        private void hookInstallApps(final Object[] calls) {
            hookMethod("android.app.ApplicationPackageManager", "getPackageInfo", String.class, int.class,
                    new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            String packageName = (String) param.args[0];
                            if (packageName != null) {
                                if (hookPackages.contains(packageName)) {
                                    param.setResult(null);
                                } else {
                                    PackageInfo info = (PackageInfo) param.getResult();
                                    if (info != null && info.applicationInfo != null) {
                                        info.applicationInfo.flags &= ~ApplicationInfo.FLAG_DEBUGGABLE;
                                        param.setResult(info);
                                    }
                                }
                            }
                            callAfterHookedMethod(param, calls);
                        }
                    });

            hookMethod("android.app.ContextImpl", "getApplicationInfo",
                    new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            ApplicationInfo info = (ApplicationInfo) param.getResult();
                            if (info != null) {
                                info.flags &= ~ApplicationInfo.FLAG_DEBUGGABLE;
                                param.setResult(info);
                            }
                            callAfterHookedMethod(param, calls);
                        }
                    });

            final long curTime = System.currentTimeMillis();
            final long sub = 12 * 60 * 60 * 1000L;

            hookMethod("android.app.ApplicationPackageManager", "getInstalledPackages", int.class,
                    new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            List<PackageInfo> list = (List<PackageInfo>) param.getResult();
                            List<PackageInfo> newList = new ArrayList<>();
                            if (list == null) {
                                return;
                            }
                            for (PackageInfo info : list) {
                                if (info != null && !hookPackages.contains(info.packageName)) {
                                    if (info.applicationInfo != null) {
                                        info.applicationInfo.flags &= ~ApplicationInfo.FLAG_DEBUGGABLE;
                                    }

                                    if (curTime - info.lastUpdateTime < sub) {
                                        info.lastUpdateTime -= sub;
                                        info.firstInstallTime -= sub;
                                    }
                                    newList.add(info);
                                }
                            }
                            param.setResult(newList);

                            callAfterHookedMethod(param, calls);
                        }
                    });

            hookMethod("android.app.ApplicationPackageManager", "getInstalledApplications", int.class,
                    new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            List<ApplicationInfo> list = (List<ApplicationInfo>) param.getResult();
                            List<ApplicationInfo> newList = new ArrayList<>();
                            if (list == null) {
                                return;
                            }
                            int flags = (int) param.args[0];
                            for (ApplicationInfo info : list) {
                                if (info != null && !hookPackages.contains(info.packageName)) {
                                    info.flags &= ~ApplicationInfo.FLAG_DEBUGGABLE;
                                    newList.add(info);
                                }
                            }
                            param.setResult(newList);

                            callAfterHookedMethod(param, calls);
                        }
                    });


            hookMethod("android.app.ApplicationPackageManager", "getInstallerPackageName", String.class,
                    new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            String name = (String) param.args[0];
                            if (hookPackages.contains(name)) {
                                param.setThrowable(new PackageManager.NameNotFoundException("package not find"));
                            }

                            callAfterHookedMethod(param, calls);
                        }
                    });
        }

        //堆栈
        private void hookStackTrace(final Object... calls) {
            hookMethod(Thread.class, "getStackTrace", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    StackTraceElement[] elements = (StackTraceElement[]) param.getResult();
                    if (elements != null) {
                        List<StackTraceElement> list = new ArrayList<>();
                        for (StackTraceElement e : elements) {
                            String stack = e.toString().toLowerCase();
                            if (!stack.contains("xposed") && !stack.contains("edhooker")) {
                                list.add(e);
                            }
                        }

                        StackTraceElement[] result = new StackTraceElement[list.size()];
                        param.setResult(list.toArray(result));
                    }

                    callAfterHookedMethod(param, calls);
                }
            });

            hookMethod(Throwable.class, "getStackTrace", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    StackTraceElement[] elements = (StackTraceElement[]) param.getResult();
                    if (elements != null) {
                        List<StackTraceElement> list = new ArrayList<>();
                        for (StackTraceElement e : elements) {
                            if (e != null) {
                                String cls = e.getClassName();
                                if (cls.equals("com.android.internal.os.ZygoteInit")) {
                                    continue;
                                }

                                if (cls.equals("com.saurik.substrate.MS$2")
                                        && e.getMethodName().equals("invoked")) {
                                    continue;
                                }

                                if (cls.equals("de.robv.android.xposed.XposedBridge")
                                        && e.getMethodName().equals("main")) {
                                    continue;
                                }

                                if (cls.equals("de.robv.android.xposed.XposedBridge")
                                        && e.getMethodName().equals("handleHookedMethod")) {
                                    continue;
                                }

                                if (cls.toLowerCase().equals("cuckoo")) {
                                    continue;
                                }

                                if (cls.toLowerCase().equals("droidbox")) {
                                    continue;
                                }

                                if (cls.toLowerCase().contains("edhooker")) {
                                    continue;
                                }

                                if (e != null && e.toString().toLowerCase().contains("xposed")) {
                                    continue;
                                }
                            }
                            list.add(e);
                        }

                        StackTraceElement[] result = new StackTraceElement[list.size()];
                        list.toArray(result);
                        param.setResult(result);
                    }
                    callAfterHookedMethod(param, calls);
                }
            });
        }

        private void handleReadLine(XC_MethodHook.MethodHookParam param, final Object[] calls) {
            String result = (String) param.getResult();
            if (result != null) {
                if (result.contains("/sbin/adbd")) {
                    param.setResult("");
                }

                if (result.contains("xpose") || result.toLowerCase().contains("edhooker")) {
                    param.setResult("");
                }

                if (result.contains("com.saurik.substrate") || result.contains("XposedBridge.jar")) {
                    param.setResult(result.replaceAll("com.saurik.substrate", "").replaceAll("XposedBridge.jar", ""));
                }
            }
            callAfterHookedMethod(param, calls);
        }

        @Override
        public String getTag() {
            return "UnXposed";
        }
    }

    public static class NetWorkHooker extends BaseHooker {

        final RequestStatsManager statsManager = RequestStatsManager.getInstance();

        public NetWorkHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        @Override
        public void hook(Object... calls) {
            hookURL(calls);
        }

        public void hookURL(final Object... calls) {
            log("start hook URL");

            if (has(FilterEnum.CategoryType.METHOD.getName(), "init")) {
                log("start hook URL String.class");
//                hookConstructor(URL.class, String.class, new XC_MethodHook() {
//                    @Override
//                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//                        super.beforeHookedMethod(param);
//                        if (param != null && param.args != null) {
//                            String host = (String) param.args[0];
//
//                            statsURL(host);
//
//                            if (!has(FilterEnum.CategoryType.PATH.getName(), host)) {
//                                return;
//                            }
//
//                            log("host = " + host);
//                        }
//                        log("current process=" + loadParams.processName);
//
//                        callBeforeHookedMethod(param, calls);
//                    }
//                });

                log("start hook URL ,URL.class ,String.class");
                hookConstructor("java.net.URL", "java.net.URL", String.class, "java.net.URLStreamHandler", new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        if (param != null && param.args != null) {
                            Object context = param.args[0];
                            String spec = String.valueOf(param.args[1]);
                            Object handler = param.args[2];

                            statsURL(spec);

                            if (!has(FilterEnum.CategoryType.PATH.getName(), spec)) {
                                return;
                            }

                            log("context = " + context + ",spec=" + spec + ",hander=" + handler);
                        }

                        callBeforeHookedMethod(param, calls);
                    }
                });

                hookConstructor("java.net.URL", String.class, String.class, int.class, String.class, "java.net.URLStreamHandler", new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        if (param != null && param.args != null) {
                            Object protocol = param.args[0];
                            String host = String.valueOf(param.args[1]);
                            Object port = param.args[2];
                            Object file = param.args[3];
                            Object handler = param.args[4];

                            statsURL(host);

                            if (!has(FilterEnum.CategoryType.PATH.getName(), host)) {
                                return;
                            }
                            log("protocol = " + protocol + ",host=" + host + ",port=" + port + ",file=" + file + ",hander=" + handler);
                        }
                        callBeforeHookedMethod(param, calls);
                    }
                });


                log("start hook Socket");

                try {
                    hookConstructor(Socket.class, String.class, int.class, "java.net.InetAddress", int.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            if (param != null && param.args.length > 0) {
                                String host = (String) param.args[0];

                                statsURL(host);

                                if (!has(FilterEnum.CategoryType.PATH.getName(), host)) {
                                    return;
                                }

                                log("socket=" + concatParams(param));
                            }
                            callBeforeHookedMethod(param, calls);
                        }
                    });

                    hookConstructor(Socket.class, String.class, int.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            if (param != null && param.args.length > 0) {
                                String host = (String) param.args[0];

                                statsURL(host);

                                if (!has(FilterEnum.CategoryType.PATH.getName(), host)) {
                                    return;
                                }

                                log("socket=" + concatParams(param));
                            }
                            callBeforeHookedMethod(param, calls);
                        }
                    });

                    hookConstructor(Socket.class, "java.net.InetAddress", int.class, new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            if (param != null && param.args.length > 0) {
                                InetAddress address = (InetAddress) param.args[0];

                                statsURL(address.getHostName());
                                log("socket=" + concatParams(param));
                            }
                            callBeforeHookedMethod(param, calls);
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }

                log("end hook Socket");

                try {
                    log("start hook OkHttpClient");
                    Class cls = loader.loadClass("okhttp3.OkHttpClient");
                    hookMethod(cls, "newCall", "okhttp3.Request", new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            if (param != null && param.args.length > 0) {
                                Object request = param.args[0];
                                log("request=" + request);

                                Object httpUrl = Reflection.get(request, "okhttp3.Request", "url");

                                if (httpUrl != null) {
                                    statsURL(httpUrl.toString());
                                }
                            }

                            callBeforeHookedMethod(param, calls);
                        }
                    });
                    log("end hook OkHttpClient");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        private synchronized void statsURL(String host) {
            Integer result = statsManager.addRequestStats(host, loadParams.processName);
            log(host + " packageName=" + loadParams.packageName);
            log("进程：" + loadParams.processName + "," + host + " has requested " + result + " times");
        }

        @Override
        public String getTag() {
            return "Hooker.NetWork";
        }
    }

    public static class ContextHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.Context";
        }

        @Override
        public void hook(Object... calls) {
            if (!has(FilterEnum.CategoryType.CLASS.getName(), "android.app.ContextImpl")) {
                return;
            }

            hookStartService(calls);
            hookBindService(calls);
            hookSendBroadCast(calls);
            hookRegisterReceiver(calls);
        }

        public ContextHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        public void hookStartService(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "startService")) {
                return;
            }

            if (containContent(FilterEnum.CategoryType.METHOD.getName(), "startService")) {
                hookMethod("android.app.ContextImpl", "startService", Intent.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        Intent intent = (Intent) param.args[0];
                        log("StartService={" + "intent=" + getIntentInfo(intent) + "}");
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }
        }

        public void hookBindService(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "bindService")) {
                return;
            }
            hookMethod("android.app.ContextImpl", "bindService", Intent.class, ServiceConnection.class, int.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Intent intent = (Intent) param.args[0];
                    ServiceConnection connection = (ServiceConnection) param.args[1];
                    Object flag = param.args[2];
                    log("BindService={" + "intent=" + getIntentInfo(intent) + ",connection=" + connection + ",flag=" + flag + "}");
                    callBeforeHookedMethod(param, calls);
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                }
            });
        }

        public void hookSendBroadCast(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "sendBroadcast")) {
                return;
            }
            hookMethod("android.app.ContextImpl", "sendBroadcast", Intent.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Intent intent = (Intent) param.args[0];
                    log("sendBroadCast={" + "intent=" + getIntentInfo(intent) + "}");
                    callBeforeHookedMethod(param, calls);
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                }
            });
        }

        public void hookRegisterReceiver(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "registerReceiver")) {
                return;
            }
            hookMethod("android.app.ContextImpl", "registerReceiver", BroadcastReceiver.class, IntentFilter.class,
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            super.beforeHookedMethod(param);
                            if (param.args != null && param.args.length == 2) {
                                BroadcastReceiver receiver = (BroadcastReceiver) param.args[0];
                                IntentFilter filter = (IntentFilter) param.args[1];

                                StringBuilder builder = new StringBuilder();
                                if (filter != null) {
                                    Iterator<String> iterator = filter.actionsIterator();
                                    while (iterator.hasNext()) {
                                        String action = iterator.next();
                                        builder.append(action);
                                    }
                                }
                                log("registerReceiver.receiver=" + (receiver != null ? receiver.getClass().getName() : "") + ",filter=" + builder.toString());
                            }
                            callBeforeHookedMethod(param, calls);
                        }

                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            super.afterHookedMethod(param);
                        }
                    });
        }
    }

    public static class ActivityHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.Activity";
        }

        @Override
        public void hook(final Object... calls) {
            if (!has(FilterEnum.CategoryType.CLASS.getName(), Activity.class.getName())) {
                return;
            }

            hookStartActivity(calls);
            hookOnCreate(calls);
            hookOnResume(calls);
            hookOnPause(calls);
            hookSetContentView(calls);
        }

        public ActivityHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        public void hookStartActivity(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "startActivityForResult")) {
                return;
            }

            hookMethod(Activity.class, "startActivityForResult", Intent.class, int.class, Bundle.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    if (param != null && param.args != null) {
                        Intent intent = (Intent) param.args[0];
                        Object requestCode = param.args[1];
                        Bundle options = (Bundle) param.args[2];
                        log("startActivityForResult={" + "intent = " + getIntentInfo(intent) + ",requestCode=" + requestCode + ",option=" + getBundleInfo(options) + "}");
                    }
                    callBeforeHookedMethod(param, calls);
                }
            });
        }

        public void hookOnCreate(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "onCreate")) {
                return;
            }

            hookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Activity activity = (Activity) param.thisObject;
                    Bundle bundle = (Bundle) param.args[0];
                    log("Activity.name=" + activity.getLocalClassName() + ",onCreate=" + getBundleInfo(bundle));
                    log("Activity.intent = " + getIntentInfo(activity.getIntent()));
                    callBeforeHookedMethod(param, calls);
                }
            });
        }

        public void hookOnResume(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "onResume")) {
                return;
            }

            XposedHelpers.findAndHookMethod(Activity.class, "onResume", new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Activity activity = (Activity) param.thisObject;
                    log("Activity.name=" + activity.getLocalClassName() + ",onResume=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }
            });
        }

        public void hookOnPause(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "onPause")) {
                return;
            }
            hookMethod(Activity.class, "onPause", new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Activity activity = (Activity) param.thisObject;
                    log("Activity.name=" + activity.getLocalClassName() + ",onPause=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }
            });
        }

        public void hookSetContentView(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "setContentView")) {
                return;
            }

            hookMethod(Activity.class, "setContentView", int.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Activity activity = (Activity) param.thisObject;
                    log("Activity.name=" + activity.getLocalClassName() + ",setContentView=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }
            });

            hookMethod(Activity.class, "setContentView", View.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Activity activity = (Activity) param.thisObject;
                    View v = (View) param.args[0];
                    String name = "";
                    if (v != null) {
                        name = v.getClass().getName();
                    }
                    log("Activity.name=" + activity.getLocalClassName() + ",setContentView.name=" + name);
                    callBeforeHookedMethod(param, calls);
                }
            });
        }
    }

    public static class ServiceHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.Service";
        }

        @Override
        public void hook(Object... calls) {
            if (mPackageInfo != null && mPackageInfo.services != null) {
                for (ServiceInfo info : mPackageInfo.services) {
                    if (has(FilterEnum.CategoryType.CLASS.getName(), info.name)) {
                        hookOnStartCommand(info.name, calls);
                    }
                }
            }
        }

        private PackageInfo mPackageInfo;

        public ServiceHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
            try {
                mPackageInfo = context.getPackageManager().getPackageInfo(lparam.packageName,
                        PackageManager.GET_SERVICES);

            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
        }

        public void hookOnStartCommand(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "onStartCommand")) {
                return;
            }

            final Class cls = Reflect.getAvailableClass((String) calls[0], loader, "onStartCommand", Intent.class, int.class, int.class);

            if (cls == null) {
                return;
            }

            log("service.class=" + cls.getName());
            XposedHelpers.findAndHookMethod(cls, "onStartCommand", Intent.class, int.class, int.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Intent intent = (Intent) param.args[0];
                    Object flags = param.args[1];
                    Object startId = param.args[2];
                    log("cls=" + cls.getName() + ":onStartCommand={" + "intent=" + getIntentInfo(intent) + ",flags=" + flags + ",startId=" + startId + "}");
                    callBeforeHookedMethod(param, calls[1]);
                }
            });
        }
    }

    public static class ContentProviderHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.Provider";
        }

        @Override
        public void hook(Object... calls) {
            // TODO: 2020/1/15  luyd
            if (mPackageInfo != null && mPackageInfo.providers != null) {
                for (ProviderInfo p : mPackageInfo.providers) {
                    HLog.o("provider.class=" + p.name);
                    try {
                        if (has(FilterEnum.CategoryType.CLASS.getName(), p.name)) {
                            hookProviderConstructor(p.name, calls);
                            hookQuery(p.name, calls);
                            hookInsert(p.name, calls);
                            hookUpdate(p.name, calls);
                            hookDelete(p.name, calls);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        private PackageInfo mPackageInfo;

        public ContentProviderHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
            try {
                mPackageInfo = context.getPackageManager().getPackageInfo(lparam.packageName,
                        PackageManager.GET_PROVIDERS);

            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
        }

        public void hookProviderConstructor(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "init")) {
                return;
            }

            try {
                String name = (String) calls[0];
                Class cls = loader.loadClass(name);
                if (cls == null) {
                    return;
                }

                hookAllConstructor(cls, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        callBeforeHookedMethod(param, calls[1]);
                    }
                });
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        public void hookQuery(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "query")) {
                return;
            }

            Class cls = Reflect.getAvailableClass((String) calls[0], loader, "query", Uri.class, String[].class, String.class, String[].class, String.class);

            if (cls == null) {
                return;
            }


            hookMethod(cls, "query", Uri.class, String[].class, String.class, String[].class, String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("query=" + concatParams(param));
                    callBeforeHookedMethod(param, calls[1]);
                }
            });
        }

        public void hookInsert(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "insert")) {
                return;
            }
            Class cls = Reflect.getAvailableClass((String) calls[0], loader, "insert", Uri.class, ContentValues.class);

            if (cls == null) {
                return;
            }

            hookMethod(cls, "insert", Uri.class, ContentValues.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("insert=" + concatParams(param));
                    callBeforeHookedMethod(param, calls[1]);
                }
            });
        }

        public void hookUpdate(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "update")) {
                return;
            }
            Class cls = Reflect.getAvailableClass((String) calls[0], loader, "update", Uri.class, ContentValues.class, String.class, String[].class);

            if (cls == null) {
                return;
            }

            hookMethod(cls, "update", Uri.class, ContentValues.class, String.class, String[].class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("update=" + concatParams(param));
                    callBeforeHookedMethod(param, calls[1]);
                }
            });
        }

        public void hookDelete(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "delete")) {
                return;
            }
            Class cls = Reflect.getAvailableClass((String) calls[0], loader, "delete", Uri.class, String.class, String[].class);

            if (cls == null) {
                return;
            }

            hookMethod(cls, "delete", Uri.class, String.class, String[].class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("delete=" + concatParams(param));
                    callBeforeHookedMethod(param, calls[1]);
                }
            });
        }
    }

    public static class BroadCastHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.BroadCast";
        }

        @Override
        public void hook(Object... calls) {
            if (mPackageInfo != null && mPackageInfo.receivers != null) {
                for (ActivityInfo info : mPackageInfo.receivers) {
                    if (has(FilterEnum.CategoryType.CLASS.getName(), info.name)) {
                        hookOnReceive(info.name, calls);
                    }
                }
            }
        }

        private PackageInfo mPackageInfo;

        public BroadCastHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
            try {
                mPackageInfo = context.getPackageManager().getPackageInfo(lparam.packageName,
                        PackageManager.GET_RECEIVERS);

            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
        }

        public void hookOnReceive(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "onReceive")) {
                return;
            }

            final Class cls = Reflect.getAvailableClass((String) calls[0], loader, "onReceive", Context.class, Intent.class);

            if (cls == null || cls.getName().equals(BroadcastReceiver.class.getName())) {
                return;
            }

            log("broadcast.class=" + cls.getName());
            hookMethod(cls, "onReceive", Context.class, Intent.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    Intent intent = (Intent) param.args[1];
                    log("cls=" + cls.getName() + ",onReceive.intent=" + getIntentInfo(intent));
                    callBeforeHookedMethod(param, calls[1]);
                }
            });
        }
    }

    public static class SharedPreferenceHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.SP";
        }

        @Override
        public void hook(Object... calls) {
            hookPut(calls);
            hookGet(calls);
        }

        public SharedPreferenceHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        public void hookPut(final Object... calls) {
            if (!has(FilterEnum.CategoryType.CLASS.getName(), "android.app.SharedPreferencesImpl$EditorImpl")) {
                return;
            }

            if (has(FilterEnum.CategoryType.METHOD.getName(), "putString")) {
                hookMethod("android.app.SharedPreferencesImpl$EditorImpl", "putString", String.class, String.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        log("putString=" + concatParams(param));
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }

            if (has(FilterEnum.CategoryType.METHOD.getName(), "putInt")) {
                hookMethod("android.app.SharedPreferencesImpl$EditorImpl", "putInt", String.class, int.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        log("putInt=" + concatParams(param));
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }

            if (has(FilterEnum.CategoryType.METHOD.getName(), "putLong")) {
                hookMethod("android.app.SharedPreferencesImpl$EditorImpl", "putLong", String.class, long.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        log("putLong=" + concatParams(param));
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }

            if (has(FilterEnum.CategoryType.METHOD.getName(), "putFloat")) {
                hookMethod("android.app.SharedPreferencesImpl$EditorImpl", "putFloat", String.class, float.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        log("putFloat=" + concatParams(param));
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }

            if (has(FilterEnum.CategoryType.METHOD.getName(), "putBoolean")) {
                hookMethod("android.app.SharedPreferencesImpl$EditorImpl", "putBoolean", String.class, boolean.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        log("putBoolean=" + concatParams(param));
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }
        }

        public void hookGet(final Object... calls) {
            if (!has(FilterEnum.CategoryType.CLASS.getName(), "android.app.SharedPreferencesImpl")) {
                return;
            }

            if (has(FilterEnum.CategoryType.METHOD.getName(), "getString")) {
                hookMethod("android.app.SharedPreferencesImpl", "getString", String.class, String.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        log("getString=" + concatParams(param));
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }

            if (has(FilterEnum.CategoryType.METHOD.getName(), "getInt")) {
                hookMethod("android.app.SharedPreferencesImpl", "getInt", String.class, int.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        log("getInt=" + concatParams(param));
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }

            if (has(FilterEnum.CategoryType.METHOD.getName(), "getLong")) {
                hookMethod("android.app.SharedPreferencesImpl", "getLong", String.class, long.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        log("getLong=" + concatParams(param));
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }

            if (has(FilterEnum.CategoryType.METHOD.getName(), "getFloat")) {
                hookMethod("android.app.SharedPreferencesImpl", "getFloat", String.class, float.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        log("getFloat=" + concatParams(param));
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }

            if (has(FilterEnum.CategoryType.METHOD.getName(), "getBoolean")) {
                hookMethod("android.app.SharedPreferencesImpl", "getBoolean", String.class, boolean.class, new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        super.beforeHookedMethod(param);
                        log("getBoolean=" + concatParams(param));
                        callBeforeHookedMethod(param, calls);
                    }
                });
            }
        }
    }

    public static class FileHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.File";
        }

        @Override
        public void hook(Object... calls) {
            try {
                hookFileConstructor(calls);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public FileHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        public void hookFileConstructor(final Object... calls) throws Exception {
            Class cls = Class.forName("java.io.File");

            if (!has(FilterEnum.CategoryType.CLASS.getName(), cls.getName())) {
                return;
            }

            if (!has(FilterEnum.CategoryType.METHOD.getName(), "init")) {
                return;
            }

            hookAllConstructor(cls, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    File f = (File) param.thisObject;
                    if (f != null) {
                        String filePath = f.getAbsolutePath();

                        if (!has(FilterEnum.CategoryType.PATH.getName(), filePath)) {
                            return;
                        }

                        if (filePath.startsWith("/data") || filePath.contains(loadParams.packageName)) {
                            log("file.constructor=" + concatParams(param));
                        }
                    }
                    callAfterHookedMethod(param, calls);
                }
            });
        }
    }

    public static class UriHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.Uri";
        }

        @Override
        public void hook(Object... calls) {
            if (!has(FilterEnum.CategoryType.CLASS.getName(), Uri.class.getName())) {
                return;
            }

            try {
                hookUriFromFile(calls);
                hookUriParse(calls);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public UriHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        public void hookUriFromFile(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "fromFile")) {
                return;
            }

            hookMethod(Uri.class, "fromFile", File.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    if (param != null && param.args != null) {
                        File uriFile = (File) param.args[0];

                        if (uriFile == null) {
                            return;
                        }

                        if (!has(FilterEnum.CategoryType.PATH.getName(), uriFile.getAbsolutePath())) {
                            return;
                        }

                        log("Uri.fromFile,file.path=" + uriFile.getAbsolutePath());
                    }
                    callBeforeHookedMethod(param, calls);
                }
            });
        }

        public void hookUriParse(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "parse")) {
                return;
            }

            hookMethod(Uri.class, "parse", String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    if (param == null || param.args.length == 0) {
                        return;
                    }
                    String uriString = String.valueOf(param.args[0]);

                    if (uriString != null && uriString.startsWith("content://setting.api/gl?")) {
                        return;
                    }

                    if (!has(FilterEnum.CategoryType.PATH.getName(), uriString)) {
                        return;
                    }

                    if (uriString.startsWith("https://googleads.g.doubleclick.net/mads/static")) {
                        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
                        for (StackTraceElement e : elements) {
                            log("strace=" + e.toString());
                        }

                    }

                    log("Uri.parse=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }
            });
        }
    }

    public static class JsonHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.Json";
        }

        @Override
        public void hook(Object... calls) {
            if (!has(FilterEnum.CategoryType.CLASS.getName(), JSONObject.class.getName())) {
                return;
            }
            try {
                hookPut(calls);
                hookGet(calls);
                hookOpt(calls);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public JsonHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        public void hookPut(final Object... calls) throws Exception {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "put")) {
                return;
            }

            Class jClazz = loader.loadClass("org.json.JSONObject");
            hookAllMethod(jClazz, "put", new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("json.put=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }
            });
        }

        public void hookGet(final Object... calls) throws Exception {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "get")) {
                return;
            }
            Class jClazz = loader.loadClass("org.json.JSONObject");
            hookMethod(jClazz, "get", String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("json.put=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }
            });
        }

        public void hookOpt(final Object... calls) throws Exception {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "opt")) {
                return;
            }
            Class jClazz = loader.loadClass("org.json.JSONObject");
            hookMethod(jClazz, "opt", String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("json.opt=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }
            });
        }
    }

    public static class SqliteHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.Sqlite";
        }

        @Override
        public void hook(Object... calls) {
            if (!has(FilterEnum.CategoryType.CLASS.getName(), SQLiteDatabase.class.getName())) {
                return;
            }

            hookRawQuery(calls);
            hookQuery(calls);
            hookInsert(calls);
            hookUpdate(calls);
            hookDelete(calls);
        }

        public SqliteHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        public void hookRawQuery(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "rawQuery")) {
                return;
            }

            hookMethod(SQLiteDatabase.class, "rawQuery", String.class, String[].class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("rawQuery=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                }
            });
        }

        public void hookQuery(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "query")) {
                return;
            }
            hookMethod(SQLiteDatabase.class, "query", boolean.class, String.class, String[].class, String.class, String[].class,
                    String.class, String.class, String.class, String.class,
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            super.beforeHookedMethod(param);
                            log("query=" + concatParams(param));
                            callBeforeHookedMethod(param, calls);
                        }

                        @Override
                        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                            super.afterHookedMethod(param);
                        }
                    });
        }

        public void hookInsert(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "insert")) {
                return;
            }
            hookMethod(SQLiteDatabase.class, "insert", String.class, String.class, ContentValues.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("insert=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                }
            });
        }

        public void hookUpdate(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "update")) {
                return;
            }
            hookMethod(SQLiteDatabase.class, "update", String.class, ContentValues.class, String.class, String[].class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("update=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                }
            });
        }

        public void hookDelete(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "delete")) {
                return;
            }
            hookMethod(SQLiteDatabase.class, "delete", String.class, String.class, String[].class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    log("delete=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    super.afterHookedMethod(param);
                }
            });
        }
    }

    public static class WebViewHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.WebView";
        }

        @Override
        public void hook(Object... calls) {
            if (!has(FilterEnum.CategoryType.CLASS.getName(), WebView.class.getName())) {
                return;
            }
            hookLoadUrl(calls);
            hookEvaluateJavascript(calls);
            hookLoadData(calls);
        }

        public WebViewHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        public void hookLoadUrl(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "loadUrl")) {
                return;
            }

            hookMethod(WebView.class, "loadUrl", String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    String url = (String) param.args[0];

                    if (!has(FilterEnum.CategoryType.PATH.getName(), url)) {
                        return;
                    }

                    log("loadUrl=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }
            });
        }

        public void hookEvaluateJavascript(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "evaluateJavascript")) {
                return;
            }

            hookMethod(WebView.class, "evaluateJavascript", String.class, ValueCallback.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    String script = (String) param.args[0];

                    if (!has(FilterEnum.CategoryType.PATH.getName(), script)) {
                        return;
                    }

                    log("evaluateJavascript=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }
            });
        }

        public void hookLoadData(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "loadData")) {
                return;
            }

            hookMethod(WebView.class, "loadData", String.class, String.class, String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    String data = (String) param.args[0];

                    if (!has(FilterEnum.CategoryType.PATH.getName(), data)) {
                        return;
                    }

                    log("loadData=" + concatParams(param));
                    callBeforeHookedMethod(param, calls);
                }
            });

            if (!has(FilterEnum.CategoryType.METHOD.getName(), "loadDataWithBaseURL")) {
                return;
            }

            hookMethod(WebView.class, "loadDataWithBaseURL", String.class, String.class, String.class, String.class, String.class,
                    new XC_MethodHook() {
                        @Override
                        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                            super.beforeHookedMethod(param);
                            String base = (String) param.args[0];
                            String data = (String) param.args[1];

                            if (!has(FilterEnum.CategoryType.PATH.getName(), base + data)) {
                                return;
                            }

                            log("loadDataWithBaseURL=" + concatParams(param));
                            callBeforeHookedMethod(param, calls);
                        }
                    });
        }
    }

    public static class PermissionHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.Permission";
        }

        @Override
        public void hook(Object... calls) {
            checkPermission(calls);
        }

        private void checkPermission(Object... calls) {
            try {
                PackageManager pm = context.getPackageManager();
                PermissionGroupInfo pgi;
                PackageInfo pkgInfo = pm.getPackageInfo(loadParams.packageName, PackageManager.GET_PERMISSIONS);
                String sharedPkgList[] = pkgInfo.requestedPermissions;

                if (sharedPkgList == null || sharedPkgList.length == 0) {
                    return;
                }

                List<String> errors = new ArrayList<>();

                StringBuilder builder = new StringBuilder();
                for (int i = 0; i < sharedPkgList.length; i++) {
                    String permName = sharedPkgList[i];

                    if (!has(FilterEnum.CategoryType.PATH.getName(), permName)) {
                        errors.add(permName);
                    }

                    PermissionInfo tmpPermInfo = pm.getPermissionInfo(permName, 0);
                    builder.append(i + "-" + permName + "\n");

                    if (tmpPermInfo.group != null) {
                        pgi = pm.getPermissionGroupInfo(tmpPermInfo.group, 0);
                        builder.append(i + "-" + pgi.loadLabel(pm) + "\n");
                    }
                    builder.append(i + "-" + tmpPermInfo.loadLabel(pm) + "\n");
                    builder.append(i + "-" + tmpPermInfo.loadDescription(pm) + "\n");
                    builder.append("-----------------------------\n");
                }
                log("权限列表：\n" + builder.toString());

                if (errors.size() != 0) {
                    callBack(null, null, errors, calls);
                }
            } catch (PackageManager.NameNotFoundException e) {
                log("Could'nt retrieve permissions for package");
            }
        }


        public PermissionHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

    }

    public static class StringHooker extends BaseHooker {

        @Override
        public String getTag() {
            return "Hooker.String";
        }

        @Override
        public void hook(Object... calls) {
            if (!has(FilterEnum.CategoryType.CLASS.getName(), String.class.getName())) {
                return;
            }

            hookString(calls);
        }

        public StringHooker(Context context, XC_LoadPackage.LoadPackageParam lparam) {
            super(context, lparam);
        }

        private void hookString(final Object... calls) {
            if (!has(FilterEnum.CategoryType.METHOD.getName(), "init")) {
                return;
            }

            hookAllConstructor(String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
                    String object = param.thisObject.toString();

                    if (!has(FilterEnum.CategoryType.PATH.getName(), object)) {
                        return;
                    }

                    int args = param.args.length;
                    StringBuilder sb = new StringBuilder("hookString :");
                    for (int i = 0; i < args; i++) {
                        sb.append("arg" + i).append("=" + param.args[i] + "; ");
                    }
                    log(sb.toString());
                    callBeforeHookedMethod(param, calls);
                }
            });


            if (!has(FilterEnum.CategoryType.METHOD.getName(), "startsWith")) {
                return;
            }

            hookMethod(String.class, "startsWith", String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    String object = (String) param.thisObject;

                    if ("content://setting.api/gl?".equals(object)) {
                        return;
                    }

                    if (!has(FilterEnum.CategoryType.PATH.getName(), object)) {
                        return;
                    }

                    log("hookString.startsWith: " + param.thisObject.toString() + ",   " + param.args[0]);
                    callBeforeHookedMethod(param, calls);
                }
            });

            if (!has(FilterEnum.CategoryType.METHOD.getName(), "endsWith")) {
                return;
            }

            hookMethod(String.class, "endsWith", String.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    String object = (String) param.thisObject;

                    if (!has(FilterEnum.CategoryType.PATH.getName(), object)) {
                        return;
                    }

                    log("hookString.endsWith: " + param.thisObject.toString() + ",   " + param.args[0]);
                    callBeforeHookedMethod(param, calls);
                }
            });

            if (!has(FilterEnum.CategoryType.METHOD.getName(), "equals")) {
                return;
            }

            hookMethod(String.class, "equals", Object.class, new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    super.beforeHookedMethod(param);
                    String object = (String) param.thisObject;

                    if (!has(FilterEnum.CategoryType.PATH.getName(), object)) {
                        return;
                    }

                    log("hookString.equals: " + param.thisObject.toString() + ",   " + param.args[0]);
                    callBeforeHookedMethod(param, calls);
                }
            });
        }
    }
}
